# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.

# This source code is licensed under the license found in the
# LICENSE file in the root directory of this source tree.
# --------------------------------------------------------
# LARS optimizer, implementation from MoCo v3:
# https://github.com/facebookresearch/moco-v3
# --------------------------------------------------------

import torch


class LARS(torch.optim.Optimizer):
    """
    LARS优化器（Layer-wise Adaptive Rate Scaling），不对小于等于1D的参数进行学习率缩放或权重衰减。
    该优化器通常用于大规模分布式训练，例如在大规模神经网络中使用。
    """

    def __init__(self, params, lr=0, weight_decay=0, momentum=0.9, trust_coefficient=0.001):
        """
        初始化LARS优化器。

        参数：
            params (iterable): 要优化的参数列表，通常是模型的参数。
            lr (float, optional): 学习率，默认为0。
            weight_decay (float, optional): 权重衰减系数，默认为0。
            momentum (float, optional): 动量，默认为0.9。
            trust_coefficient (float, optional): 信任系数，默认为0.001。
        """
        # 定义优化器的默认参数，包括学习率、权重衰减、动量和信任系数
        defaults = dict(lr=lr, weight_decay=weight_decay, momentum=momentum, trust_coefficient=trust_coefficient)
        # 通过父类的初始化方法初始化优化器
        super().__init__(params, defaults)

    @torch.no_grad()
    def step(self):
        """
        执行一次参数更新步骤，使用LARS算法进行更新。
        该步骤会更新所有参数，根据参数的维度，是否应用LARS算法中的信任系数。
        """
        # 遍历所有参数组
        for g in self.param_groups:
            # 遍历当前参数组中的所有参数
            for p in g['params']:
                dp = p.grad  # 获取参数p的梯度

                # 如果梯度为None，跳过当前参数
                if dp is None:
                    continue

                # 对于非1D参数（即，通常是权重矩阵等大于1维的参数），应用LARS算法
                if p.ndim > 1:  # 如果不是规范化层的gamma/beta参数或偏置
                    # 在梯度上添加权重衰减
                    dp = dp.add(p, alpha=g['weight_decay'])
                    param_norm = torch.norm(p)  # 参数的L2范数
                    update_norm = torch.norm(dp)  # 更新的梯度的L2范数
                    one = torch.ones_like(param_norm)  # 创建与param_norm相同形状的张量，值为1

                    # 如果参数的范数和更新的范数大于0，则计算LARS信任系数
                    q = torch.where(param_norm > 0.,
                                    torch.where(update_norm > 0,
                                    (g['trust_coefficient'] * param_norm / update_norm), one),
                                    one)
                    # 对梯度应用信任系数缩放
                    dp = dp.mul(q)

                # 获取当前参数的状态
                param_state = self.state[p]
                # 如果当前参数没有'调动'（mu），初始化为零
                if 'mu' not in param_state:
                    param_state['mu'] = torch.zeros_like(p)
                mu = param_state['mu']

                # 更新mu，考虑动量项
                mu.mul_(g['momentum']).add_(dp)
                # 使用更新后的mu更新参数
                p.add_(mu, alpha=-g['lr'])
